import java.io.IOException;
import java.net.DatagramPacket;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import membership.SimpleDBMembership;

import remoteprocedurecall.RPCClient;
import remoteprocedurecall.RPCDestination;
import remoteprocedurecall.RPCServer;
import session.ErrorCodes;
import session.SessionData;
import session.SessionDataTable;
import values.VALUES;

/**
 * Servlet implementation class GreetingServlet
 */
public class GreetingServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
    private String cookieSEP = ",";
    private Integer cookieIDPOS = 0;
    private Integer cookieVERSIONPOS = 1;
    private Integer cookieIPPORTPOS = 2;
	//use these as the UID for now
	String myId = "0"; //in the future, this will be uniquely defined for each server
	Integer cookieCount=0;
	//session data
	SessionDataTable sessionTable;
	
    /**
     * @throws InterruptedException 
     * @see HttpServlet#HttpServlet()
     */
    public GreetingServlet() throws InterruptedException {
        super();  
        sessionTable = new SessionDataTable(VALUES.BLOCK_TIMEOUT, VALUES.SESSION_TIMEOUT);
        RPCServer rpc = new RPCServer();
        rpc.start();
        //System.out.println("Starting the servlet" + SimpleDBMembership.getInstance().myIpAddress + " : " +  SimpleDBMembership.getInstance().myPort );
        while(SimpleDBMembership.getInstance().myIpAddress == null || SimpleDBMembership.getInstance().myPort == null ){
        	//System.out.println("looping");
        	Thread.sleep(10);
        }
        //System.out.println("trying to set my id " + SimpleDBMembership.getInstance().myIpAddress+":"+SimpleDBMembership.getInstance().myPort);
    	myId = SimpleDBMembership.getInstance().myIpAddress+":"+SimpleDBMembership.getInstance().myPort;
    }

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setAttribute("activesession", true);
		
		//get cookie for this session
		Cookie myCookie = getCookie(request);
		
		System.out.println("Cookie Before = "+myCookie.getValue());
		
		//get the session... 
		SessionData mySession = getSession(myCookie);
		
		//make changes to the session...
		updateSession(mySession,request,myCookie);
		
		//Perform distributed write
		distributedWrite(myCookie,mySession);
		
		//add cookie to response
		response.addCookie(myCookie);
		
		System.out.println("Cookie After = "+myCookie.getValue());
		
		//load jsp
		try{
			request.setAttribute("message", mySession.message);
			request.setAttribute("views", mySession.views);
			request.setAttribute("address", SimpleDBMembership.getInstance().myIpAddress+":"+SimpleDBMembership.getInstance().myPort);
			getServletConfig().getServletContext().getRequestDispatcher(
	        	"/GreetingServlet.jsp").forward(request,response);
		} catch(Exception e){}
	}

	private void distributedWrite(Cookie myCookie, SessionData mySession) throws IOException {
		//update cookie, use data from packets to determine comment
		List<RPCDestination> recList = RPCClient.serverPacketsToRPCDestinationList(mySession);
		System.out.println("Recieved by "+recList.size());
		String comment = "";
		
		//update the cookie only if the session was written
		myCookie.setValue(myCookie.getValue().split(cookieSEP)[cookieIDPOS]);
		myCookie.setValue(myCookie.getValue()+cookieSEP+mySession.version);
		for(RPCDestination rd:recList){
			comment=comment+rd.destAddress+":"+String.valueOf(rd.destPort)+cookieSEP;
		}
		if(comment.endsWith(",")){
			comment=comment.substring(0, comment.length()-1);
		}
		myCookie.setValue(myCookie.getValue()+cookieSEP+comment);
		//add session to local cache
		sessionTable.setSession(mySession);
		
	}

	/**
	 * updateSession - updates the session and request attributes
	 * @param mySession
	 * @param request
	 */
	private void updateSession(SessionData mySession, HttpServletRequest request, Cookie myCookie) {
		
		if(request.getParameter("cmd")!=null){
			if(request.getParameter("cmd").toString().equals("Replace")){
				//update message, reset views
				mySession.message=request.getParameter("replacement").toString();
				mySession.views=1;
			} else if(request.getParameter("cmd").toString().equals("Refresh")){
				//don't do anything special, views is already incremented
				mySession.views++;
			} else if(request.getParameter("cmd").toString().equals("Logout")){
				//remove session
				request.setAttribute("activesession", false);
				sessionTable.removeSession(mySession.sessionID);
				myCookie.setMaxAge(0);
			}
		}
		
		//if session wasn't terminated...
		if((Boolean)request.getAttribute("activesession")==true){
			//fake set values code, dont let the string get bigger than 1kb
			//mySession.values=myId.toString();
			//while(mySession.values.getBytes().length+myId.toString().getBytes().length<1024){
			//	mySession.values+=','+myId.toString();
			//}
			//mySession.version++;
			//if this session is already in the table, sychronize on its tuple
			if(sessionTable.getSession(mySession.sessionID,mySession.version)!=null){
				synchronized(sessionTable.getSession(mySession.sessionID,mySession.version)){
					ErrorCodes ec = sessionTable.setSession(mySession);
				}	
			} else {
				ErrorCodes ec = sessionTable.setSession(mySession);
			}
		}
		
		//update version
		mySession.version = mySession.version+1;
		
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
	 */
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doGet(request, response);
	}

	/**
	 * getCookie - gets the CS5300SESSION cookie, creates a new one if one does not exist for the client
	 * @param request - http request
	 * @return CS5300SESSION cookie
	 */
	private Cookie getCookie(HttpServletRequest request){
		//get request cookie, generate a new one if none is given
		Cookie[] cookies = request.getCookies();
		Cookie myCookie=null;
		if(cookies!=null){
			for(Cookie c:cookies){
				if(c.getName().equals("CS5300SESSION")){
					myCookie=c;
					break;
				}
			}
		}
		if(myCookie==null){
			//synchronize on cookieCount so concurrent new users don't get the same cookieCount value
			synchronized(cookieCount){
				myCookie = new Cookie("CS5300SESSION", String.valueOf(myId)+"."+String.valueOf(cookieCount)+cookieSEP+"0"+cookieSEP);
				cookieCount++;
			}	
		}
		return myCookie;
	}
	
	/**
	 * getSession - gets the current session information, creates a new session if necessary
	 * @param myCookie - CS5300SESSION cookie
	 * @return - session data
	 * @throws IOException 
	 */
	private SessionData getSession(Cookie myCookie) {
		//see if session is in cache
		SessionData mySession = sessionTable.getSession(myCookie.getValue().trim().split(cookieSEP)[cookieIDPOS],Integer.parseInt(myCookie.getValue().trim().split(cookieSEP)[cookieVERSIONPOS]));
		if(mySession!=null){
			System.out.println("Got Session!");
			return mySession;
		} else {
			System.out.println("Session not in cache");
		}
		//extract destinations from cookie
		List<RPCDestination> destList = new ArrayList<RPCDestination>();
		try{
			String[] ipPortList = myCookie.getValue().trim().split(cookieSEP);
			for(int i=cookieIPPORTPOS; i<ipPortList.length; ++i){
				String[] splitIpp = ipPortList[i].split(":");
				destList.add(new RPCDestination(splitIpp[0],new Integer(splitIpp[1])));
			}
		}catch(Exception e){
			//new cookie, session hasn't been written
		}
		//perform ssm get, or make a new session
		try{
			if(destList.size()!=0){
				DatagramPacket myPacket = RPCClient.ssmGet(myCookie.getValue().trim().split(cookieSEP)[cookieIDPOS], Integer.parseInt(myCookie.getValue().trim().split(cookieSEP)[cookieVERSIONPOS]), destList);
				mySession = RPCClient.serverPacketToSessionData(myPacket);
			}
		} catch(Exception e){
		}
		if(mySession==null){
			//synchronized(cookieCount){
			//	myCookie = new Cookie("CS5300SESSION", String.valueOf(myId)+"."+String.valueOf(cookieCount)+cookieSEP+"0"+cookieSEP);
			//	cookieCount++;
			//}	
			mySession = new SessionData(myCookie.getValue().trim().split(cookieSEP)[cookieIDPOS], Integer.parseInt(myCookie.getValue().trim().split(cookieSEP)[cookieVERSIONPOS]), 0l,
					"Hello World!", 1, "");
		}
		return mySession;
	}
}
